package org.osm2world.core.world.network;

import java.util.ArrayList;
import java.util.List;

import org.osm2world.core.map_data.data.MapNode;
import org.osm2world.core.map_data.data.MapSegment;
import org.osm2world.core.map_data.data.MapWaySegment;
import org.osm2world.core.math.InvalidGeometryException;
import org.osm2world.core.math.SimplePolygonXZ;
import org.osm2world.core.math.VectorXZ;
import org.osm2world.core.world.creation.NetworkCalculator;
import org.osm2world.core.world.data.OutlineNodeWorldObject;

public abstract class VisibleConnectorNodeWorldObject
	extends OutlineNodeWorldObject {

	protected boolean informationProvided;
	
	protected VectorXZ cutVector;
	protected VectorXZ startPos;
	protected VectorXZ endPos;
	protected float startWidth;
	protected float endWidth;
	
	/**
	 * returns the length required by this node representation.
	 * Adjacent lines will be pushed back accordingly.
	 * 
	 * If this is 0, this has the same effect as an invisible
	 * connector node (adjacent line representations
	 * directly touching each other). Examples where non-zero values
	 * are needed include crossings at nodes in roads.
	 * 
	 * Needs to be provided by the implementing class before the
	 * calculation in {@link NetworkCalculator} starts.
	 */
	abstract public float getLength();
	
	/**
	 * sets the results of {@link NetworkCalculator}'s calculations.
	 * Most methods in this class cannot be used until this method
	 * has provided the required information!
	 */
	public void setInformation(VectorXZ cutVector,
			VectorXZ startPos, VectorXZ endPos,
			float startWidth, float endWidth) {
		
		this.informationProvided = true;
		
		this.cutVector = cutVector;
		this.startPos = startPos;
		this.endPos = endPos;
		this.startWidth = startWidth;
		this.endWidth = endWidth;
		
	}
	
	public VisibleConnectorNodeWorldObject(MapNode node) {
		super(node);
	}
	
	@Override
	public SimplePolygonXZ getOutlinePolygonXZ() {
		
		List<VectorXZ> outlineXZ = new ArrayList<VectorXZ>(getOutlineXZ(0, 0));
		outlineXZ.addAll(getOutlineXZ(1, 1));
		
		if (outlineXZ.size() > 2) {
		
			try { //TODO better handling of broken outlines
				outlineXZ.add(outlineXZ.get(0));
				return new SimplePolygonXZ(outlineXZ);
			} catch (InvalidGeometryException e) {}
			
			}
		
		return null;
		
	}
	
	/**
	 * provides outline for the areas covered by the connector.
	 * 
	 * The from and to indices refer to the list
	 * returned by the underlying {@link MapNode}'s
	 * {@link MapNode#getConnectedSegments()} method.
	 */
	protected List<VectorXZ> getOutlineXZ(int from, int to) {
	
		checkInformationProvided();
		
		List<VectorXZ> outline = new ArrayList<VectorXZ>();
	
		List<MapSegment> segments = node.getConnectedSegments();
	
		assert from >= 0 && from < segments.size();
		assert to >= 0 && to < segments.size();
	
		if (((from == 1 && to == 0) || (from == 0 && to == 1))) {
						
			if (from == 0) {
				
				VectorXZ pos1 = startPos
					.add(cutVector.mult(startWidth));
	
				VectorXZ pos2 = endPos
					.add(cutVector.mult(endWidth));
				
				outline.add(pos1);
				outline.add(pos2);
				
			} else {
	
				VectorXZ pos1 = endPos
					.subtract(cutVector.mult(endWidth));
	
				VectorXZ pos2 = startPos
					.subtract(cutVector.mult(startWidth));
				
				outline.add(pos1);
				outline.add(pos2);
				
			}
			
		} else if (from == to
				&& segments.get(from) instanceof MapWaySegment) { //usually at the end of a noexit road
			
			MapWaySegment segment = (MapWaySegment) segments.get(from);
			
			if (segment.getPrimaryRepresentation() instanceof NetworkWaySegmentWorldObject) {
				
				NetworkWaySegmentWorldObject rep =
					(NetworkWaySegmentWorldObject) segment.getPrimaryRepresentation();
				
				//TODO: the calculations for pos1/2 should be part of the NetworkLineRepresentation (it's used quite often)
				
				if (segment.getEndNode() == node) { //inbound segment
					
					VectorXZ pos1 = node.getPos()
						.add(rep.getEndOffset())
						.add(rep.getEndCutVector().mult(rep.getWidth()/2));
					
					VectorXZ pos2 = node.getPos()
						.add(rep.getEndOffset())
						.subtract(rep.getEndCutVector().mult(rep.getWidth()/2));
										
					outline.add(pos1);
					outline.add(pos2);
					
				} else { //outbound segment
	
					VectorXZ pos1 = node.getPos()
						.add(rep.getStartOffset())
						.subtract(rep.getStartCutVector().mult(rep.getWidth()/2));
					
					VectorXZ pos2 = node.getPos()
						.add(rep.getStartOffset())
						.add(rep.getStartCutVector().mult(rep.getWidth()/2));
	
					outline.add(pos1);
					outline.add(pos2);
										
				}
				
			}
			
		}
		
		return outline;
		
	}
	
	/**
	 * throws an IllegalStateException if information hasn't been
	 * provided by a {@link NetworkCalculator}
	 */
	private void checkInformationProvided() throws IllegalStateException {
		if (!informationProvided) {
			throw new IllegalStateException("no connector information"
					+ " has been set for this representation.\n"
					+ "node: " + node);
		}
	}
	
}
